package idservice;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.jsfirst.logs.GeneralLog;
import org.apache.commons.lang.StringUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;

public class GetSeqNoService {
    private static String ModelName = "GetSeqNoService";

    private DataSource datasource;

    public DataSource getDatasource() {
        return this.datasource;
    }

    public void setDatasource(DataSource datasource) {
        this.datasource = datasource;
    }

    private int cacheSize = 100;

    public int getCacheSize() {
        return this.cacheSize;
    }

    public void setCacheSize(int cacheSize) {
        this.cacheSize = cacheSize;
    }

    private String forUpdate = " for update";

    public String getForUpdate() {
        return this.forUpdate;
    }

    public void setForUpdate(String forUpdate) {
        this.forUpdate = forUpdate;
    }

    private static class SeqNo {
        long seqNo = 1L;
        long cacheMax = 0L;
        int maxLen;
        boolean circle;
        private String tblName;
        private String keyNam;
        private String KeyVal;
        private String SeqNam;
        DataSource datasource;

        public DataSource getDatasource() {
            return this.datasource;
        }

        public void setDatasource(DataSource datasource) {
            this.datasource = datasource;
        }

        public synchronized long get(int cacheSize, String forUpdate)
                throws Exception {
            long ret = this.seqNo++;
            if (ret > this.cacheMax) {
                load(cacheSize, forUpdate);
                ret = this.seqNo++;
            }
            return ret;
        }

        private synchronized void load(int cacheSize, String forUpdate)
                throws Exception {
            GeneralLog.info(ModelName, "load seqno from db!");
            GeneralLog.info(ModelName, "db中获取序号!");
            Connection conn = null;
            try {
                conn = this.datasource.getConnection();
                conn.setAutoCommit(false);
                boolean next = query(conn, forUpdate);
                update(conn, next, cacheSize);
                conn.commit();
                GeneralLog.info(ModelName, "GetSeqNoService  cache->[" + Long.valueOf(this.seqNo) + "[" + Long.valueOf(this.cacheMax) + "]");
            } finally {
                if (conn != null) {
                    try {
                        conn.rollback();
                    } catch (Exception e) {
                        GeneralLog.error(ModelName, "rollback error?!");
                    } finally {
                        conn.close();
                    }
                }
            }
        }

        private void update(Connection conn, boolean next, int cacheSize)
                throws SQLException {
            PreparedStatement stmt = null;
            try {
                if (!next) {
                    this.seqNo = 1L;
                    this.cacheMax = (cacheSize + this.seqNo);
                    String initSql = "insert into %s (%s,%s) values ('%s','%s')";
                    String initValue = StringUtils.leftPad(
                            String.valueOf(this.cacheMax), this.maxLen, "0");
                    initSql = String.format(initSql, new Object[]{this.tblName, this.keyNam, this.SeqNam,
                            this.KeyVal, initValue});
                    GeneralLog.info(ModelName, String.format("GetSeqNoService  ===>[%s]", new Object[]{
                            initSql}));
                    stmt = conn.prepareStatement(initSql);
                    int ab = stmt.executeUpdate();
                    GeneralLog.info(ModelName, String.format("GetSeqNoService  ===>[%s][%d]", new Object[]{
                            initSql, Integer.valueOf(ab)}));
                } else {
                    this.cacheMax = (this.seqNo + cacheSize);
                    if (String.valueOf(this.cacheMax).length() > this.maxLen) {
                        if (this.circle) {
                            this.seqNo = 1L;
                            this.cacheMax = (this.seqNo + cacheSize);
                        } else {
                            throw new RuntimeException(this.tblName + this.keyNam +
                                    this.cacheMax);
                        }
                    }
                    String sqlCmd = "UPDATE %s SET %s = '%s'  where %s = '%s'";
                    String value = StringUtils.leftPad(
                            String.valueOf(this.cacheMax), this.maxLen, "0");
                    sqlCmd = String.format(sqlCmd, new Object[]{this.tblName, this.SeqNam, value,
                            this.keyNam, this.KeyVal});
                    GeneralLog.info(ModelName, String.format("GetSeqNoService  sql->[%s]", new Object[]{
                            sqlCmd}));
                    stmt = conn.prepareStatement(sqlCmd);
                    int ab = stmt.executeUpdate();
                    GeneralLog.info(ModelName, String.format("GetSeqNoService  sql->[%s][%d]", new Object[]{
                            sqlCmd, Integer.valueOf(ab)}));
                }
            } finally {
                closeStmt(stmt);
            }
        }

        private boolean query(Connection conn, String forUpdate)
                throws SQLException {
            PreparedStatement stmt = null;
            String sqlCmd = "SELECT %s FROM %s where %s = '%s' " +
                    forUpdate;
            sqlCmd = String.format(sqlCmd, new Object[]{this.SeqNam, this.tblName, this.keyNam, this.KeyVal});
            GeneralLog.info(ModelName, String.format("GetSeqNoService  sql->[%s]", new Object[]{sqlCmd}));
            ResultSet rs = null;
            boolean next;
            try {
                stmt = conn.prepareStatement(sqlCmd);
                rs = stmt.executeQuery();
                next = rs.next();
                if (next) {
                    this.seqNo = (Integer.parseInt(rs.getString(1).trim()) + 1);
                    GeneralLog.info(ModelName, String.format(
                            "GetSeqNoService  sql->[%s],seqNo=[%d]", new Object[]{sqlCmd,
                                    Long.valueOf(this.seqNo)}));
                }
            } finally {
                if (rs != null) {
                    try {
                        rs.close();
                    } catch (Throwable e) {
                        GeneralLog.error(ModelName, "rs.close() error?!");
                    }
                }
                closeStmt(stmt);
            }
            return next;
        }

        private void closeStmt(PreparedStatement stmt) {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (Throwable e) {
                    GeneralLog.error(ModelName,"stmt.close() error?!");
                }
            }
        }
    }

    private final Cache<String, SeqNo> cache = CacheBuilder.newBuilder()
            .maximumSize(128L).expireAfterAccess(24L, TimeUnit.HOURS)
            .build(new CacheLoader<String, SeqNo>() {
                public SeqNo load(String key)
                        throws Exception {
                    return (SeqNo) Context.getInstance(SeqNo.class);
                }
            });

    public String getSeqNoNew(String keyNam, String KeyVal, String SeqNam, String tblName, String len, String circledString)
            throws Exception {
        SeqNo key = new SeqNo();
        key.setDatasource(this.datasource);

        boolean circled = false;
        if (StringUtils.equals(circledString, "1")) {
            circled = true;
        }
        int maxLen = Integer.parseInt(len);
        if (maxLen > 9) {
            throw new IllegalArgumentException("最大长度上限为[9]");
        }
        key.keyNam = keyNam;
        key.KeyVal = KeyVal;
        key.tblName = tblName;
        key.SeqNam = SeqNam;
        key.circle = circled;
        key.maxLen = maxLen;
        Context.setInstance(SeqNo.class, key);
        String keys = "#" + keyNam + "#" + KeyVal + "#" + tblName + "#" +
                SeqNam + "#" + circled + "#" + maxLen + "#";
        long seqNo = ((SeqNo) this.cache.get(keys)).get(this.cacheSize, this.forUpdate);
        return StringUtils.leftPad(String.valueOf(seqNo), maxLen, "0");
    }

    public String getSeqNoNew(String keyVal, String len, String circledString)
            throws Exception {
        return getSeqNoNew(TdCommConstants.PUB_GETSEQNO_KETNAME, keyVal, TdCommConstants.PUB_GETSEQNO_KEYVAL, TdCommConstants.PUB_GETSEQNO_TABLENAME, len, circledString);
    }
}
